package org.arch_learn.ioc_aop.ioc;

import org.arch_learn.flashboot.autoconfigure.*;
import org.arch_learn.flashboot.context.Autowired;
import org.arch_learn.flashboot.context.Component;
import org.arch_learn.flashboot.context.GlobalContext;
import org.arch_learn.ioc_aop.annos.Service;
import org.arch_learn.ioc_aop.aop.ProxyFactory;
import org.arch_learn.ioc_aop.aop.TransactionManager;
import org.arch_learn.ioc_aop.utils.ScanUtils;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

public class BeanFactory {
    private static Map<String, Object> singletonPool = GlobalContext.globalSingletonPool;

    public static void start() {
        String starterPackage = GlobalContext.starterPackage;
        try {
            //扫描全局相应文件
            ScanUtils.doScanPackage(starterPackage);
            //初始化ioc单例池
            initSingletonPool();
            //属性注入
            doAutowired();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //为全局单例池中的实例实现依赖注入
    private static void doAutowired() {
        if (singletonPool.size() == 0) return;
        singletonPool.entrySet().stream()
                .forEach(v -> {
                    String objKey = v.getKey();
                    Object objValue = v.getValue();
                    Class<?> aClass = objValue.getClass();
                    Field[] declaredFields = aClass.getDeclaredFields();
                    for (Field f : declaredFields) {
                        if (!f.isAnnotationPresent(Autowired.class)) {
                            continue;
                        }
                        Autowired annotation = f.getAnnotation(Autowired.class);
                        String value = annotation.value();
                        Object o = null;
                        if (value.length() == 0) {
                            String name = f.getType().getClass().getName();
                            o = singletonPool.get(name);
                        } else {
                            o = singletonPool.get(value);
                        }
                        f.setAccessible(true);
                        try {
                            f.set(objValue, o);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    //put proxy object into singleton pool when obj class is Annotated by Service.class
                    if (aClass.isAnnotationPresent(Service.class)) {
                        TransactionManager transactionManager = GlobalContext.getInstance(TransactionManager.class);
                        if (null != transactionManager) {
                            Object jdkProxyObject = ProxyFactory.getJDKProxyObject(objValue, transactionManager);
                            singletonPool.put(objKey, jdkProxyObject);
                        }
                    }
                });
    }

    private static void initSingletonPool() throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //先处理自动配置类
        Set<String> configClassNames = ScanUtils.getConfigClassNames();
        loadConfigurations(configClassNames);
        //再处理业务类
        Set<String> classNames = ScanUtils.getClassNames();
        load(classNames);
    }

    private static void loadConfigurations(Set<String> configClassNames) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException {
        List<Class<?>> classes = configClassNames.stream().map(v -> {
            Class<?> clazz = null;
            try {
                clazz = Class.forName(v);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            return clazz;
        }).filter(v -> v != null).collect(Collectors.toList());

        //1.加载配置信息到@ConfigurationProperties标注的类实例中
        loadProperties(classes);
        //2.使用递归，按照依赖顺序处理自动配置类
        List<Class<?>> topAutoConfigClasses = classes.stream().
                filter(v -> !v.isAnnotationPresent(ConfigAfter.class)
                        && !v.isAnnotationPresent(ConditionalOnBean.class))
                .collect(Collectors.toList());
        classes.removeAll(topAutoConfigClasses);
        activateAutoConfigClasses(topAutoConfigClasses, classes);
    }

    //利用反射以及注解@ConfigAfter和@ConditionalOnBean的顺序逻辑
    //有序执行自动配置类中被@Bean标注的的方法实现自动配置
    private static void activateAutoConfigClasses(List<Class<?>> classesToBeHandled, List<Class<?>> leftClasses) throws InstantiationException, IllegalAccessException, InvocationTargetException {
        if (classesToBeHandled.size() == 0) return;
        List<String> classNames = classesToBeHandled.stream()
                .map(Class::getName).collect(Collectors.toList());
        for (Class<?> clazz : classesToBeHandled) {
            Object o = clazz.newInstance();
            Method[] declaredMethods = clazz.getDeclaredMethods();
            for (Method m : declaredMethods) {
                if (m.isAnnotationPresent(Bean.class)) {
                    Type[] genericParameterTypes = m.getGenericParameterTypes();
                    Object[] args = new Object[genericParameterTypes.length];
                    for (int i = 0; i < args.length; i++) {
                        String argTypeName = genericParameterTypes[i].getTypeName();
                        args[i] = singletonPool.get(argTypeName);
                    }
                    Object bean = m.invoke(o, args);
                    String returnTypeName = m.getGenericReturnType().getTypeName();
                    singletonPool.put(returnTypeName, bean);
                }
            }
        }
        classesToBeHandled = leftClasses.stream()
                .filter(lc -> {
                    ConfigAfter annotation = lc.getAnnotation(ConfigAfter.class);
                    Class<?>[] value = (annotation == null ? new Class<?>[0] : annotation.value());

                    ConditionalOnBean conditionalOnBeanAnno = lc.getAnnotation(ConditionalOnBean.class);
                    Class<?>[] conditionalOnBeanValues =
                            (conditionalOnBeanAnno == null ? new Class<?>[0] : conditionalOnBeanAnno.value());

                    return Arrays.stream(value).allMatch(v -> classNames.contains(v.getName()))
                            && Arrays.stream(conditionalOnBeanValues)
                            .allMatch(v -> singletonPool.values().stream().anyMatch(s -> v.isInstance(s)));
                }).collect(Collectors.toList());
        leftClasses.removeAll(classesToBeHandled);
        activateAutoConfigClasses(classesToBeHandled, leftClasses);
    }

    //装配配置文件application.properties里面的配置信息到所有被@ConfigurationProperties标注的的类实例里
    //并将这些实例放到全局单例池中
    private static void loadProperties(List<Class<?>> classes) throws IOException, InstantiationException, IllegalAccessException {
        List<Class<?>> configClassesWithEnableConfigurationPropertiesAnno = classes.stream()
                .filter(v -> v.isAnnotationPresent(EnableConfigurationProperties.class))
                .collect(Collectors.toList());

        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("application.properties");
        Properties properties = new Properties();
        properties.load(resourceAsStream);

        List<Class<?>> propertiesClasses = new ArrayList<>();
        configClassesWithEnableConfigurationPropertiesAnno.forEach(v -> {
            EnableConfigurationProperties annotation = v.getAnnotation(EnableConfigurationProperties.class);
            propertiesClasses.addAll(Arrays.asList(annotation.value()));
        });

        for (Class<?> clazz : propertiesClasses) {

            Object propertyInstance = clazz.newInstance();
            Field[] declaredFields = clazz.getDeclaredFields();
            ConfigurationProperties configurationPropertiesAnno = clazz.getAnnotation(ConfigurationProperties.class);
            String prefix = configurationPropertiesAnno.prefix();
            String prefixWithDot = prefix + ".";
            Map<String, Object> map = properties.entrySet().stream()
                    .filter(entry -> ((String) entry.getKey()).startsWith(prefixWithDot))
                    .collect(Collectors.toMap(entry -> ((String) entry.getKey()).replace(prefixWithDot, ""), entry -> entry.getValue()));
            for (Field field : declaredFields) {
                field.setAccessible(true);
                String fieldName = field.getName();
                String simpleName = field.getType().getSimpleName();
                Object fieldValue = map.get(fieldName);
                String strValue = String.valueOf(fieldValue);
                if (simpleName.equals("String")) {
                    field.set(propertyInstance, strValue);
                } else {
                    Integer integerValue = Integer.valueOf(strValue);
                    field.set(propertyInstance, integerValue);
                }
            }
            singletonPool.put(clazz.getName(), propertyInstance);
        }
    }

    //加载项目工程中的业务/配置类
    private static void load(Set<String> classNames) {
        try {
            for (String name : classNames) {
                Class<?> clazz = Class.forName(name);
                if (clazz.isAnnotationPresent(Component.class)) {
                    initAndPutIntoSingletonPool(clazz, "");
                } else if (clazz.isAnnotationPresent(Service.class)) {
                    String value = clazz.getAnnotation(Service.class).value();
                    String alias = value.length() == 0 ? clazz.getName() : value;

                    initAndPutIntoSingletonPool(clazz, alias);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void initAndPutIntoSingletonPool(Class<?> clazz, String name) throws InstantiationException, IllegalAccessException {
        Object o = clazz.newInstance();
        if (name.length() != 0) {
            singletonPool.put(name, o);
        } else {
            singletonPool.put(clazz.getName(), o);
        }
    }

    public static Object getBean(String key) {
        return singletonPool.get(key);
    }
}
